package com.bl.file.service;

import com.bl.file.config.NettyProperties;
import com.bl.file.util.ByteBufUploadHelper;
import com.bl.file.util.ResponseUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

/**
 * @author yangxiaodong
 * @version 1.0
 * @description: TODO
 * @date 2025/7/2 15:11
 */
@Slf4j
public class StreamFileHandler extends SimpleChannelInboundHandler<HttpObject> {

    private FileChannel fileChannel;
    private RandomAccessFile raf;
    private boolean writing = false;
    private ByteBufUploadHelper uploadHelper;
    private final NettyProperties nettyProperties;

    public StreamFileHandler(NettyProperties nettyProperties) {
        this.nettyProperties = nettyProperties;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;
            if (request.uri().startsWith(nettyProperties.getUploadUrl())) {
                //上传
                upload(ctx, request);
            } else if (request.uri().startsWith(nettyProperties.getDownloadUrl())) {
                //下载
                download(ctx, request);
            }
        }
        //上传逻辑
        if (msg instanceof HttpContent && writing) {
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            uploadHelper.writeToChannel(buf, fileChannel);
            // 每 3 秒打印一次
            uploadHelper.tryLogProgress(3);

            if (msg instanceof LastHttpContent) {
                fileChannel.close();
                raf.close();
                writing = false;
                double avg = uploadHelper.getAverageSpeedMBps();
                double size = uploadHelper.getTotalBytesWritten() / 1024.0 / 1024.0;
                double totalSecond = uploadHelper.getTotalSecond();
                log.info("完成上传,总大小: {} MB,平均速度: {} MB/s, 总耗时:{} 秒", String.format("%.2f", size), String.format("%.2f", avg), String.format("%.2f", totalSecond));
                ResponseUtil.sendResponse(ctx, HttpResponseStatus.OK, uploadHelper.getFileName());
            }
        }
    }

    /**
     * @param ctx
     * @param request
     * @description: 上传文件
     * @return: void
     * @author yangxiaodong
     * @date: 2025/7/2 17:02
     */
    private void upload(ChannelHandlerContext ctx, HttpRequest request) throws IOException {
        QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
        Map<String, List<String>> params = decoder.parameters();
        List<String> names = params.get("filename");
        String filename = null;
        if (CollectionUtils.isEmpty(names)) {
            filename = System.currentTimeMillis() + ".bin";
        } else {
            filename = names.get(0);
        }
        if (!request.uri().startsWith(nettyProperties.getUploadUrl()) || request.method() != HttpMethod.POST) {
            ResponseUtil.sendResponse(ctx, HttpResponseStatus.NOT_FOUND, "Invalid path or method");
            return;
        }
        Path outputPath = Paths.get(nettyProperties.getFilePath()).resolve(filename);
        Files.createDirectories(outputPath.getParent());
        raf = new RandomAccessFile(outputPath.toFile(), "rw");
        fileChannel = raf.getChannel();
        writing = true;
        uploadHelper = new ByteBufUploadHelper(filename);
        log.info("开始接收文件: {}", filename);
    }

    /**
     * @param ctx
     * @param request
     * @description: 下载
     * @return: void
     * @author yangxiaodong
     * @date: 2025/7/2 17:02
     */
    private void download(ChannelHandlerContext ctx, HttpRequest request) throws IOException, URISyntaxException {
        URI uri = new URI(request.uri());
        String downloadUrl = nettyProperties.getDownloadUrl();
        String filename = uri.getPath().substring(downloadUrl.length());
        Path path = Paths.get(nettyProperties.getFilePath()).resolve(filename);
        if (!Files.exists(path)) {
            ResponseUtil.sendResponse(ctx, HttpResponseStatus.NOT_FOUND, "File not found");
            return;
        }
        long fileLength = Files.size(path);
        RandomAccessFile randomAccessFile = new RandomAccessFile(path.toFile(), "r");
        FileChannel fileChannel = randomAccessFile.getChannel();
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, Files.probeContentType(path));
        response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION,
                "attachment; filename=\"" + URLEncoder.encode(filename, String.valueOf(StandardCharsets.UTF_8)) + "\"");

        ctx.write(response);
        ChannelFuture sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
        log.info("开始下载文件: {}", filename);
        long startTime = System.nanoTime();
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            long lastTime = startTime;
            long lastProgress = 0;

            @Override
            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                long now = System.nanoTime();
                long intervalNs = now - lastTime;
                // 每3秒打印一次
                if (intervalNs > 3_000_000_000L) {
                    long delta = progress - lastProgress;
                    double speedMBps = delta / 1024.0 / 1024.0 / (intervalNs / 1_000_000_000.0);
                    log.info(String.format("下载速度: %.2f MB/s,已传输: %d / %d bytes", speedMBps, progress, total));
                    lastTime = now;
                    lastProgress = progress;
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture future) {
                long totalTimeNs = System.nanoTime() - startTime;
                double totalTimeSec = totalTimeNs / 1_000_000_000.0;
                double speedMBps = fileLength / 1024.0 / 1024.0 / totalTimeSec;
                if (future.isSuccess()) {
                    log.info(String.format("下载完成,总大小:%d bytes,平均速度:%.2f MB/s,用时:%.2f 秒",
                            fileLength, speedMBps, totalTimeSec));
                } else {
                    log.error("下载失败", future.cause());
                }
                try {
                    fileChannel.close();
                    randomAccessFile.close();
                } catch (IOException e) {
                    log.warn("关闭文件流异常", e);
                }
                ctx.close();
            }
        });
        ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        lastContentFuture.addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("文件处理异常: {}", cause.getMessage(), cause);
        // 关闭连接
        ctx.close();
    }
}
